Part1
This exercise concerns the clinical descriptions of tumours from The Cancer Genome Archive. It was previously downloaded from GEO and has undergone some minor alterations. See the script process_tcga_clinical.R.
The data are provided as the file tcga_clinical.tsv in the raw_data directory of the r_crash_course.zip file
Exercise: What function from readr would you use to read the file tcga_clinical.tsv into R? Read the file in. What are the number of rows and columns?
library(readr)
data <- read_tsv("raw_data/tcga_clinical.tsv")
Warning: One or more parsing issues, see `problems()` for details
Rows: 7706 Columns: 420
── Column specification ───────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (395): bcr_patient_barcode, bcr_patient_uuid, form_completion_date, prospective...
dbl (23): initial_pathologic_dx_year, age_at_diagnosis, percent_blasts_peripheral_...
lgl (2): sarcomatoid_features, sarcomatoid_percent_of_tumor
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
data
You should find that the data frame contains a great deal of columns; far too many to be useful. We would like to keep the columns containing the age of the patient, and the tumour stage in our analysis. Rather than opening-up the file, or Viewing the file in RStudio, we can use a couple of helper functions to identify the relevant column names.
Exercise: Use the select function in conjunction with contains and starts_with to identify columns that have Age or Stage information their name. The code should look like the following (you will need to fill-in the dots).
The functions contains and starts_with perform similar operations when used to select columns from a data frame. To use either, and to use the select function, we first have to load the dplyr library. Using the contains function will identify all columns that have a particular text pattern somewhere in their name. If we wanted all the columns with “age” in the name, the following wouldn’t be a good choice as it would also identify columns with “stage” in.
library(dplyr)
select(data, contains("age"))
But if we wanted all the columns regarding “stage”, contains would be a good choice
select(data, contains("stage"))
Since the age-related columns start with “age” we can use the starts_with function instead.
select(data, starts_with("age"))
select(data, contains("age"),
-contains("stage"),
-contains("agent"),
-contains("heritage"),
-contains("percentage"))
Exercise: Use the select function to create a new data frame that contains the following columns. These are not the actual columns names - Tumour site - Race - Gender - Age at diagnosis - Dead / Alive Status You can add extra columns if you wish
See below for example output
library(tidyverse)
clin <- readr::read_tsv("raw_data/tcga_clinical.tsv")
Warning: One or more parsing issues, see `problems()` for details
Rows: 7706 Columns: 420
── Column specification ───────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (395): bcr_patient_barcode, bcr_patient_uuid, form_completion_date, prospective...
dbl (23): initial_pathologic_dx_year, age_at_diagnosis, percent_blasts_peripheral_...
lgl (2): sarcomatoid_features, sarcomatoid_percent_of_tumor
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
data <- select(clin,
tumor_tissue_site,
race,
gender,
age_at_initial_pathologic_diagnosis,
vital_status)
data
Exercise: Use the dplyr function called count to tabulate how which sites are included in the data. Re-arrange the output from count using arrange to determine the most common type of cancer in the dataset.
See below for example output
count(data, tumor_tissue_site) %>% arrange(desc(n))
NA
Exercise: Not all samples have an entry for tumour type. Use the filter function to create a table with valid entries for tumor_tissue_site. Create a barplot to show display the number of occurences of each tumour type
HINT: An easy way to make the labels on the x-axis more legible is to use the coord_flip function
ggplot(data, aes(x=...)) + geom_bar() + coord_flip()
See below for example output
filter(data,!is.na(tumor_tissue_site)) %>%
filter(tumor_tissue_site != "[Not Available]") %>%
ggplot(aes(x = tumor_tissue_site)) + geom_bar() + coord_flip()

Part2
We would like to visualise the age of diagnosis, and eventually compare between different disease types, The code we might think to use initially could look like:-
## assuming your filtered clinical data is called data
ggplot(data, aes(x = age_at_initial_pathologic_diagnosis)) + geom_density()
Warning: Groups with fewer than two data points have been dropped.
Warning: Groups with fewer than two data points have been dropped.
Warning in max(ids, na.rm = TRUE) :
no non-missing arguments to max; returning -Inf
Warning in max(ids, na.rm = TRUE) :
no non-missing arguments to max; returning -Inf

This doesn’t look like the desired output though. If we re-visit the data frame and print the “age” column we notice that the entries in the column are stored as “chr”. i.e. characters or text
select(data, age_at_initial_pathologic_diagnosis)
This has occurred because some entries are “[Not Available]” rather than a number or NA. As soon as R finds any text within the column, it treats everything in the column as text.
These entries can be filtered in the same manner as previously (when filtering the tissue type column), but this does not solve the problem entirely.
data %>%
filter(age_at_initial_pathologic_diagnosis != "[Not Available]") %>%
ggplot(aes(x = age_at_initial_pathologic_diagnosis)) + geom_density()
Warning: Groups with fewer than two data points have been dropped.
Warning: Groups with fewer than two data points have been dropped.
Warning in max(ids, na.rm = TRUE) :
no non-missing arguments to max; returning -Inf
Warning in max(ids, na.rm = TRUE) :
no non-missing arguments to max; returning -Inf

We need to add an additional step which will force R to treat the data in the age_at_initial_pathologic_diagnosis column as numerical data. Such a conversion can be done using the as.numeric function and the mutate function can be used to modify the age_at_initial_pathologic_diagnosis column to contain the numeric values
Exercise: Use mutate and as.numeric to convert the values in age_at_initial_pathologic_diagnosis into numbers. You will still need to remove the [Not Available] values beforehand. Now try and create the density plot.
data %>%
filter(age_at_initial_pathologic_diagnosis != "[Not Available]") %>%
mutate(age_at_initial_pathologic_diagnosis = as.numeric(age_at_initial_pathologic_diagnosis)) %>%
ggplot(aes(x = age_at_initial_pathologic_diagnosis)) + geom_density()

Exercise: Use the facet_wrap function to compare the distribution of ages between different tumour types
data %>%
filter(age_at_initial_pathologic_diagnosis != "[Not Available]") %>%
filter(tumor_tissue_site != "[Not Available]") %>%
mutate(age_at_initial_pathologic_diagnosis = as.numeric(age_at_initial_pathologic_diagnosis)) %>%
ggplot(aes(x = age_at_initial_pathologic_diagnosis)) + geom_density() + facet_wrap(~tumor_tissue_site)
Warning: Groups with fewer than two data points have been dropped.
Warning: Groups with fewer than two data points have been dropped.
Warning in max(ids, na.rm = TRUE) :
no non-missing arguments to max; returning -Inf
Warning in max(ids, na.rm = TRUE) :
no non-missing arguments to max; returning -Inf

Exercise: Do any tumour types have a different age of diagnosis between males and females? Use a boxplot to find out
data %>%
filter(age_at_initial_pathologic_diagnosis != "[Not Available]") %>%
filter(tumor_tissue_site != "[Not Available]") %>%
mutate(age_at_initial_pathologic_diagnosis = as.numeric(age_at_initial_pathologic_diagnosis)) %>%
ggplot(aes(x= gender, y = age_at_initial_pathologic_diagnosis)) + geom_boxplot() + facet_wrap(~tumor_tissue_site)

Lets now look at gender split for each cancer type. As a first step, we can group the data by gender and tissue type and obtain counts.
data %>%
group_by(tumor_tissue_site,gender) %>%
filter(tumor_tissue_site != "[Not Available]") %>%
summarise(N = n())
`summarise()` has grouped output by 'tumor_tissue_site'. You can override using the `.groups` argument.
These data are ready for plotting, but for comparisons we need to take into account the total number of each tissue type. We can create frequencies rather than absolute numbers by dividing by the total number of cases.
data %>%
group_by(tumor_tissue_site,gender) %>%
filter(tumor_tissue_site != "[Not Available]") %>%
summarise(N = n()) %>%
mutate(freq = N / sum(N))
`summarise()` has grouped output by 'tumor_tissue_site'. You can override using the `.groups` argument.
Note that the order of the grouping is important here. If we reversed it to gender then tumor_tissue_site the frequencies would be calculated using the total of males of females.
data %>%
group_by(gender,tumor_tissue_site) %>%
filter(tumor_tissue_site != "[Not Available]") %>%
summarise(N = n()) %>%
mutate(freq = N / sum(N))
`summarise()` has grouped output by 'gender'. You can override using the `.groups` argument.
Exercise: Create a plot to show the gender split in cases of each tumor type.
data %>%
group_by(tumor_tissue_site,gender) %>%
filter(tumor_tissue_site != "[Not Available]") %>%
summarise(N = n()) %>%
mutate(freq = N / sum(N)) %>%
ggplot(aes(x = gender, y = freq)) + geom_col() + facet_wrap(~tumor_tissue_site)
`summarise()` has grouped output by 'tumor_tissue_site'. You can override using the `.groups` argument.

Exercise: Create a plot to show the proportion of patients dead or alive for each tumour type
data %>%
group_by(tumor_tissue_site,vital_status) %>%
filter(tumor_tissue_site != "[Not Available]") %>%
filter(vital_status != "[Not Available]") %>%
summarise(N = n()) %>%
mutate(freq = N / sum(N)) %>%
ggplot(aes(x = vital_status, y = freq)) + geom_col() + facet_wrap(~tumor_tissue_site)
`summarise()` has grouped output by 'tumor_tissue_site'. You can override using the `.groups` argument.

LS0tCnRpdGxlOiAiUiBjcmFzaCBjb3Vyc2UgZXhlcmNpc2UiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazogCiAgICBjc3M6IHN0eWxlc2hlZXRzL3N0eWxlcy5jc3MKLS0tCgojIFBhcnQxCgpUaGlzIGV4ZXJjaXNlIGNvbmNlcm5zIHRoZSBjbGluaWNhbCBkZXNjcmlwdGlvbnMgb2YgdHVtb3VycyBmcm9tIFRoZSBDYW5jZXIgR2Vub21lIEFyY2hpdmUuIEl0IHdhcyBwcmV2aW91c2x5IGRvd25sb2FkZWQgZnJvbSBbR0VPXShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L2dlby9xdWVyeS9hY2MuY2dpP2FjYz1HU0U2Mjk0NCkgYW5kIGhhcyB1bmRlcmdvbmUgc29tZSBtaW5vciBhbHRlcmF0aW9ucy4gU2VlIHRoZSBzY3JpcHQgW3Byb2Nlc3NfdGNnYV9jbGluaWNhbC5SXSgvcHJvY2Vzc190Y2dhX2NsaW5pY2FsLlIpLgoKVGhlIGRhdGEgYXJlIHByb3ZpZGVkIGFzIHRoZSBmaWxlIGB0Y2dhX2NsaW5pY2FsLnRzdmAgaW4gdGhlIGByYXdfZGF0YWAgZGlyZWN0b3J5IG9mIHRoZSBgcl9jcmFzaF9jb3Vyc2UuemlwYCBmaWxlCgo8ZGl2IGNsYXNzPSJleGVyY2lzZSI+CioqRXhlcmNpc2UqKjogV2hhdCBmdW5jdGlvbiBmcm9tIGByZWFkcmAgd291bGQgeW91IHVzZSB0byByZWFkIHRoZSBmaWxlIGB0Y2dhX2NsaW5pY2FsLnRzdmAgaW50byBSPyBSZWFkIHRoZSBmaWxlIGluLiBXaGF0IGFyZSB0aGUgbnVtYmVyIG9mIHJvd3MgYW5kIGNvbHVtbnM/Cgo8L2Rpdj4KCmBgYHtyfQpsaWJyYXJ5KHJlYWRyKQpkYXRhIDwtIHJlYWRfdHN2KCJyYXdfZGF0YS90Y2dhX2NsaW5pY2FsLnRzdiIpCmRhdGEKYGBgCgoKWW91IHNob3VsZCBmaW5kIHRoYXQgdGhlIGRhdGEgZnJhbWUgY29udGFpbnMgYSBncmVhdCBkZWFsIG9mIGNvbHVtbnM7IGZhciB0b28gbWFueSB0byBiZSB1c2VmdWwuIFdlIHdvdWxkIGxpa2UgdG8ga2VlcCB0aGUgY29sdW1ucyBjb250YWluaW5nIHRoZSBhZ2Ugb2YgdGhlIHBhdGllbnQsIGFuZCB0aGUgdHVtb3VyIHN0YWdlIGluIG91ciBhbmFseXNpcy4gUmF0aGVyIHRoYW4gb3BlbmluZy11cCB0aGUgZmlsZSwgb3IgYFZpZXdgaW5nIHRoZSBmaWxlIGluIFJTdHVkaW8sIHdlIGNhbiB1c2UgYSBjb3VwbGUgb2YgaGVscGVyIGZ1bmN0aW9ucyB0byBpZGVudGlmeSB0aGUgcmVsZXZhbnQgY29sdW1uIG5hbWVzLgoKPGRpdiBjbGFzcz0iZXhlcmNpc2UiPgoqKkV4ZXJjaXNlKio6IFVzZSB0aGUgYHNlbGVjdGAgZnVuY3Rpb24gaW4gY29uanVuY3Rpb24gd2l0aCBgY29udGFpbnNgIGFuZCBgc3RhcnRzX3dpdGhgIHRvIGlkZW50aWZ5IGNvbHVtbnMgdGhhdCBoYXZlIEFnZSBvciBTdGFnZSBpbmZvcm1hdGlvbiB0aGVpciBuYW1lLiBUaGUgY29kZSBzaG91bGQgbG9vayBsaWtlIHRoZSBmb2xsb3dpbmcgKHlvdSB3aWxsIG5lZWQgdG8gZmlsbC1pbiB0aGUgZG90cykuCgo8L2Rpdj4KClRoZSBmdW5jdGlvbnMgYGNvbnRhaW5zYCBhbmQgYHN0YXJ0c193aXRoYCBwZXJmb3JtIHNpbWlsYXIgb3BlcmF0aW9ucyB3aGVuIHVzZWQgdG8gc2VsZWN0IGNvbHVtbnMgZnJvbSBhIGRhdGEgZnJhbWUuIFRvIHVzZSBlaXRoZXIsIGFuZCB0byB1c2UgdGhlIGBzZWxlY3RgIGZ1bmN0aW9uLCB3ZSBmaXJzdCBoYXZlIHRvIGxvYWQgdGhlIGBkcGx5cmAgbGlicmFyeS4gVXNpbmcgdGhlIGBjb250YWluc2AgZnVuY3Rpb24gd2lsbCBpZGVudGlmeSBhbGwgY29sdW1ucyB0aGF0IGhhdmUgYSBwYXJ0aWN1bGFyIHRleHQgcGF0dGVybiBzb21ld2hlcmUgaW4gdGhlaXIgbmFtZS4gSWYgd2Ugd2FudGVkIGFsbCB0aGUgY29sdW1ucyB3aXRoICJhZ2UiIGluIHRoZSBuYW1lLCB0aGUgZm9sbG93aW5nIHdvdWxkbid0IGJlIGEgZ29vZCBjaG9pY2UgYXMgaXQgd291bGQgYWxzbyBpZGVudGlmeSBjb2x1bW5zIHdpdGggInN0YWdlIiBpbi4KCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpzZWxlY3QoZGF0YSwgY29udGFpbnMoImFnZSIpKQpgYGAKQnV0IGlmIHdlIHdhbnRlZCBhbGwgdGhlIGNvbHVtbnMgcmVnYXJkaW5nICJzdGFnZSIsIGBjb250YWluc2Agd291bGQgYmUgYSBnb29kIGNob2ljZQoKYGBge3J9CnNlbGVjdChkYXRhLCBjb250YWlucygic3RhZ2UiKSkKYGBgClNpbmNlIHRoZSBhZ2UtcmVsYXRlZCBjb2x1bW5zIHN0YXJ0IHdpdGggImFnZSIgd2UgY2FuIHVzZSB0aGUgYHN0YXJ0c193aXRoYCBmdW5jdGlvbiBpbnN0ZWFkLgoKYGBge3J9CnNlbGVjdChkYXRhLCBzdGFydHNfd2l0aCgiYWdlIikpCmBgYApgYGB7cn0Kc2VsZWN0KGRhdGEsIGNvbnRhaW5zKCJhZ2UiKSwgCiAgICAgICAtY29udGFpbnMoInN0YWdlIiksIAogICAgICAgLWNvbnRhaW5zKCJhZ2VudCIpLAogICAgICAgLWNvbnRhaW5zKCJoZXJpdGFnZSIpLAogICAgICAgLWNvbnRhaW5zKCJwZXJjZW50YWdlIikpCmBgYAoKCjxkaXYgY2xhc3M9ImV4ZXJjaXNlIj4KKipFeGVyY2lzZToqKiBVc2UgdGhlIGBzZWxlY3RgIGZ1bmN0aW9uIHRvIGNyZWF0ZSBhIG5ldyBkYXRhIGZyYW1lIHRoYXQgY29udGFpbnMgdGhlIGZvbGxvd2luZyBjb2x1bW5zLiAqKlRoZXNlIGFyZSBub3QgdGhlIGFjdHVhbCBjb2x1bW5zIG5hbWVzKioKICAtIFR1bW91ciBzaXRlCiAgLSBSYWNlCiAgLSBHZW5kZXIKICAtIEFnZSBhdCBkaWFnbm9zaXMKICAtIERlYWQgLyBBbGl2ZSBTdGF0dXMKWW91IGNhbiBhZGQgZXh0cmEgY29sdW1ucyBpZiB5b3Ugd2lzaAoKKipTZWUgYmVsb3cgZm9yIGV4YW1wbGUgb3V0cHV0KioKPC9kaXY+CgpgYGB7ciBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKY2xpbiA8LSByZWFkcjo6cmVhZF90c3YoInJhd19kYXRhL3RjZ2FfY2xpbmljYWwudHN2IikKZGF0YSA8LSBzZWxlY3QoY2xpbiwgCiAgICAgICAgICAgICAgICB0dW1vcl90aXNzdWVfc2l0ZSwKICAgICAgICAgICAgICAgIHJhY2UsCiAgICAgICAgICAgICAgICBnZW5kZXIsCiAgICAgICAgICAgICAgICBhZ2VfYXRfaW5pdGlhbF9wYXRob2xvZ2ljX2RpYWdub3NpcywKICAgICAgICAgICAgICAgIHZpdGFsX3N0YXR1cykKZGF0YQpgYGAKCgoKPGRpdiBjbGFzcz0iZXhlcmNpc2UiPgoqKkV4ZXJjaXNlOioqIFVzZSB0aGUgYGRwbHlyYCBmdW5jdGlvbiBjYWxsZWQgYGNvdW50YCB0byB0YWJ1bGF0ZSBob3cgd2hpY2ggc2l0ZXMgYXJlIGluY2x1ZGVkIGluIHRoZSBkYXRhLiBSZS1hcnJhbmdlIHRoZSBvdXRwdXQgZnJvbSBgY291bnRgIHVzaW5nIGBhcnJhbmdlYCB0byBkZXRlcm1pbmUgdGhlIG1vc3QgY29tbW9uIHR5cGUgb2YgY2FuY2VyIGluIHRoZSBkYXRhc2V0LgoKKipTZWUgYmVsb3cgZm9yIGV4YW1wbGUgb3V0cHV0KioKPC9kaXY+CgoKYGBge3IgfQpjb3VudChkYXRhLCB0dW1vcl90aXNzdWVfc2l0ZSkgJT4lIGFycmFuZ2UoZGVzYyhuKSkKCmBgYAoKPGRpdiBjbGFzcz0iZXhlcmNpc2UiPgoqKkV4ZXJjaXNlKio6IE5vdCBhbGwgc2FtcGxlcyBoYXZlIGFuIGVudHJ5IGZvciB0dW1vdXIgdHlwZS4gVXNlIHRoZSBgZmlsdGVyYCBmdW5jdGlvbiB0byBjcmVhdGUgYSB0YWJsZSB3aXRoIHZhbGlkIGVudHJpZXMgZm9yIGB0dW1vcl90aXNzdWVfc2l0ZWAuIENyZWF0ZSBhIGJhcnBsb3QgdG8gc2hvdyBkaXNwbGF5IHRoZSBudW1iZXIgb2Ygb2NjdXJlbmNlcyBvZiBlYWNoIHR1bW91ciB0eXBlCgpISU5UOiBBbiBlYXN5IHdheSB0byBtYWtlIHRoZSBsYWJlbHMgb24gdGhlIHgtYXhpcyBtb3JlIGxlZ2libGUgaXMgdG8gdXNlIHRoZSBgY29vcmRfZmxpcGAgZnVuY3Rpb24KCmBgYHtyIGV2YWw9RkFMU0V9CmdncGxvdChkYXRhLCBhZXMoeD0uLi4pKSArIGdlb21fYmFyKCkgKyBjb29yZF9mbGlwKCkKYGBgCgoqKlNlZSBiZWxvdyBmb3IgZXhhbXBsZSBvdXRwdXQqKgo8L2Rpdj4KCmBgYHtyfQogIGZpbHRlcihkYXRhLCFpcy5uYSh0dW1vcl90aXNzdWVfc2l0ZSkpICU+JSAKICBmaWx0ZXIodHVtb3JfdGlzc3VlX3NpdGUgIT0gIltOb3QgQXZhaWxhYmxlXSIpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSB0dW1vcl90aXNzdWVfc2l0ZSkpICsgZ2VvbV9iYXIoKSArIGNvb3JkX2ZsaXAoKQpgYGAKCgoKCiMgUGFydDIKCldlIHdvdWxkIGxpa2UgdG8gdmlzdWFsaXNlIHRoZSBhZ2Ugb2YgZGlhZ25vc2lzLCBhbmQgZXZlbnR1YWxseSBjb21wYXJlIGJldHdlZW4gZGlmZmVyZW50IGRpc2Vhc2UgdHlwZXMsIFRoZSBjb2RlIHdlIG1pZ2h0IHRoaW5rIHRvIHVzZSBpbml0aWFsbHkgY291bGQgbG9vayBsaWtlOi0KCmBgYHtyfQojIyBhc3N1bWluZyB5b3VyIGZpbHRlcmVkIGNsaW5pY2FsIGRhdGEgaXMgY2FsbGVkIGRhdGEKCmdncGxvdChkYXRhLCBhZXMoeCA9IGFnZV9hdF9pbml0aWFsX3BhdGhvbG9naWNfZGlhZ25vc2lzKSkgKyBnZW9tX2RlbnNpdHkoKQpgYGAKVGhpcyBkb2Vzbid0IGxvb2sgbGlrZSB0aGUgZGVzaXJlZCBvdXRwdXQgdGhvdWdoLiBJZiB3ZSByZS12aXNpdCB0aGUgZGF0YSBmcmFtZSBhbmQgcHJpbnQgdGhlICJhZ2UiIGNvbHVtbiB3ZSBub3RpY2UgdGhhdCB0aGUgZW50cmllcyBpbiB0aGUgY29sdW1uIGFyZSBzdG9yZWQgYXMgImNociIuIGkuZS4gY2hhcmFjdGVycyBvciB0ZXh0CgoKCmBgYHtyfQpzZWxlY3QoZGF0YSwgYWdlX2F0X2luaXRpYWxfcGF0aG9sb2dpY19kaWFnbm9zaXMpCmBgYAoKVGhpcyBoYXMgb2NjdXJyZWQgYmVjYXVzZSBzb21lIGVudHJpZXMgYXJlICJgW05vdCBBdmFpbGFibGVdYCIgcmF0aGVyIHRoYW4gYSBudW1iZXIgb3IgYE5BYC4gQXMgc29vbiBhcyBSIGZpbmRzIGFueSB0ZXh0IHdpdGhpbiB0aGUgY29sdW1uLCBpdCB0cmVhdHMgZXZlcnl0aGluZyBpbiB0aGUgY29sdW1uIGFzIHRleHQuCgpUaGVzZSBlbnRyaWVzIGNhbiBiZSBmaWx0ZXJlZCBpbiB0aGUgc2FtZSBtYW5uZXIgYXMgcHJldmlvdXNseSAod2hlbiBmaWx0ZXJpbmcgdGhlIHRpc3N1ZSB0eXBlIGNvbHVtbiksIGJ1dCB0aGlzIGRvZXMgbm90IHNvbHZlIHRoZSBwcm9ibGVtIGVudGlyZWx5LiAKCmBgYHtyfQpkYXRhICU+JSAKICBmaWx0ZXIoYWdlX2F0X2luaXRpYWxfcGF0aG9sb2dpY19kaWFnbm9zaXMgIT0gIltOb3QgQXZhaWxhYmxlXSIpICU+JSAKZ2dwbG90KGFlcyh4ID0gYWdlX2F0X2luaXRpYWxfcGF0aG9sb2dpY19kaWFnbm9zaXMpKSArIGdlb21fZGVuc2l0eSgpCmBgYAoKV2UgbmVlZCB0byBhZGQgYW4gYWRkaXRpb25hbCBzdGVwIHdoaWNoIHdpbGwgZm9yY2UgUiB0byB0cmVhdCB0aGUgZGF0YSBpbiB0aGUgYGFnZV9hdF9pbml0aWFsX3BhdGhvbG9naWNfZGlhZ25vc2lzYCBjb2x1bW4gYXMgbnVtZXJpY2FsIGRhdGEuIFN1Y2ggYSBjb252ZXJzaW9uIGNhbiBiZSBkb25lIHVzaW5nIHRoZSBgYXMubnVtZXJpY2AgZnVuY3Rpb24gYW5kIHRoZSBgbXV0YXRlYCBmdW5jdGlvbiBjYW4gYmUgdXNlZCB0byBtb2RpZnkgdGhlIGBhZ2VfYXRfaW5pdGlhbF9wYXRob2xvZ2ljX2RpYWdub3Npc2AgY29sdW1uIHRvIGNvbnRhaW4gdGhlIG51bWVyaWMgdmFsdWVzCgo8ZGl2IGNsYXNzPSJleGVyY2lzZSI+CioqRXhlcmNpc2UqKjogVXNlIGBtdXRhdGVgIGFuZCBgYXMubnVtZXJpY2AgdG8gY29udmVydCB0aGUgdmFsdWVzIGluIGBhZ2VfYXRfaW5pdGlhbF9wYXRob2xvZ2ljX2RpYWdub3Npc2AgaW50byBudW1iZXJzLiBZb3Ugd2lsbCBzdGlsbCBuZWVkIHRvIHJlbW92ZSB0aGUgCmBbTm90IEF2YWlsYWJsZV1gIHZhbHVlcyBiZWZvcmVoYW5kLiBOb3cgdHJ5IGFuZCBjcmVhdGUgdGhlIGRlbnNpdHkgcGxvdC4KPC9kaXY+CgpgYGB7cn0KZGF0YSAlPiUgCiAgZmlsdGVyKGFnZV9hdF9pbml0aWFsX3BhdGhvbG9naWNfZGlhZ25vc2lzICE9ICJbTm90IEF2YWlsYWJsZV0iKSAlPiUgCiAgbXV0YXRlKGFnZV9hdF9pbml0aWFsX3BhdGhvbG9naWNfZGlhZ25vc2lzID0gYXMubnVtZXJpYyhhZ2VfYXRfaW5pdGlhbF9wYXRob2xvZ2ljX2RpYWdub3NpcykpICU+JSAKZ2dwbG90KGFlcyh4ID0gYWdlX2F0X2luaXRpYWxfcGF0aG9sb2dpY19kaWFnbm9zaXMpKSArIGdlb21fZGVuc2l0eSgpCmBgYAo8ZGl2IGNsYXNzPSJleGVyY2lzZSI+CioqRXhlcmNpc2UqKjogVXNlIHRoZSBgZmFjZXRfd3JhcGAgZnVuY3Rpb24gdG8gY29tcGFyZSB0aGUgZGlzdHJpYnV0aW9uIG9mIGFnZXMgYmV0d2VlbiBkaWZmZXJlbnQgdHVtb3VyIHR5cGVzCjwvZGl2PgoKYGBge3IgfQpkYXRhICU+JSAKICBmaWx0ZXIoYWdlX2F0X2luaXRpYWxfcGF0aG9sb2dpY19kaWFnbm9zaXMgIT0gIltOb3QgQXZhaWxhYmxlXSIpICU+JSAKICAgIGZpbHRlcih0dW1vcl90aXNzdWVfc2l0ZSAhPSAiW05vdCBBdmFpbGFibGVdIikgJT4lIAogIG11dGF0ZShhZ2VfYXRfaW5pdGlhbF9wYXRob2xvZ2ljX2RpYWdub3NpcyA9IGFzLm51bWVyaWMoYWdlX2F0X2luaXRpYWxfcGF0aG9sb2dpY19kaWFnbm9zaXMpKSAlPiUgCiBnZ3Bsb3QoYWVzKHggPSBhZ2VfYXRfaW5pdGlhbF9wYXRob2xvZ2ljX2RpYWdub3NpcykpICsgZ2VvbV9kZW5zaXR5KCkgKyBmYWNldF93cmFwKH50dW1vcl90aXNzdWVfc2l0ZSkKCmBgYAoKPGRpdiBjbGFzcz0iZXhlcmNpc2UiPgoqKkV4ZXJjaXNlKio6IERvIGFueSB0dW1vdXIgdHlwZXMgaGF2ZSBhIGRpZmZlcmVudCBhZ2Ugb2YgZGlhZ25vc2lzIGJldHdlZW4gbWFsZXMgYW5kIGZlbWFsZXM/IFVzZSBhIGJveHBsb3QgdG8gZmluZCBvdXQKPC9kaXY+CgpgYGB7cn0KZGF0YSAlPiUgCiAgZmlsdGVyKGFnZV9hdF9pbml0aWFsX3BhdGhvbG9naWNfZGlhZ25vc2lzICE9ICJbTm90IEF2YWlsYWJsZV0iKSAlPiUgCiAgICBmaWx0ZXIodHVtb3JfdGlzc3VlX3NpdGUgIT0gIltOb3QgQXZhaWxhYmxlXSIpICU+JSAKICBtdXRhdGUoYWdlX2F0X2luaXRpYWxfcGF0aG9sb2dpY19kaWFnbm9zaXMgPSBhcy5udW1lcmljKGFnZV9hdF9pbml0aWFsX3BhdGhvbG9naWNfZGlhZ25vc2lzKSkgJT4lIAogZ2dwbG90KGFlcyh4PSBnZW5kZXIsIHkgPSBhZ2VfYXRfaW5pdGlhbF9wYXRob2xvZ2ljX2RpYWdub3NpcykpICsgZ2VvbV9ib3hwbG90KCkgKyBmYWNldF93cmFwKH50dW1vcl90aXNzdWVfc2l0ZSkKYGBgCgpMZXRzIG5vdyBsb29rIGF0IGdlbmRlciBzcGxpdCBmb3IgZWFjaCBjYW5jZXIgdHlwZS4gQXMgYSBmaXJzdCBzdGVwLCB3ZSBjYW4gZ3JvdXAgdGhlIGRhdGEgYnkgZ2VuZGVyIGFuZCB0aXNzdWUgdHlwZSBhbmQgb2J0YWluIGNvdW50cy4KCmBgYHtyfQpkYXRhICU+JSAKICBncm91cF9ieSh0dW1vcl90aXNzdWVfc2l0ZSxnZW5kZXIpICU+JSAKICBmaWx0ZXIodHVtb3JfdGlzc3VlX3NpdGUgIT0gIltOb3QgQXZhaWxhYmxlXSIpICU+JSAKICBzdW1tYXJpc2UoTiA9IG4oKSkKYGBgCgpUaGVzZSBkYXRhIGFyZSByZWFkeSBmb3IgcGxvdHRpbmcsIGJ1dCBmb3IgY29tcGFyaXNvbnMgd2UgbmVlZCB0byB0YWtlIGludG8gYWNjb3VudCB0aGUgdG90YWwgbnVtYmVyIG9mIGVhY2ggdGlzc3VlIHR5cGUuIFdlIGNhbiBjcmVhdGUgZnJlcXVlbmNpZXMgcmF0aGVyIHRoYW4gYWJzb2x1dGUgbnVtYmVycyBieSBkaXZpZGluZyBieSB0aGUgdG90YWwgbnVtYmVyIG9mIGNhc2VzLgoKYGBge3J9CmRhdGEgJT4lIAogIGdyb3VwX2J5KHR1bW9yX3Rpc3N1ZV9zaXRlLGdlbmRlcikgJT4lIAogIGZpbHRlcih0dW1vcl90aXNzdWVfc2l0ZSAhPSAiW05vdCBBdmFpbGFibGVdIikgJT4lIAogIHN1bW1hcmlzZShOID0gbigpKSAlPiUgCiAgbXV0YXRlKGZyZXEgPSBOIC8gc3VtKE4pKQpgYGAKCk5vdGUgdGhhdCB0aGUgb3JkZXIgb2YgdGhlIGdyb3VwaW5nIGlzIGltcG9ydGFudCBoZXJlLiBJZiB3ZSByZXZlcnNlZCBpdCB0byBgZ2VuZGVyYCB0aGVuIGB0dW1vcl90aXNzdWVfc2l0ZWAgdGhlIGZyZXF1ZW5jaWVzIHdvdWxkIGJlIGNhbGN1bGF0ZWQgdXNpbmcgdGhlIHRvdGFsIG9mIG1hbGVzIG9mIGZlbWFsZXMuCgpgYGB7cn0KZGF0YSAlPiUgCiAgZ3JvdXBfYnkoZ2VuZGVyLHR1bW9yX3Rpc3N1ZV9zaXRlKSAlPiUgCiAgZmlsdGVyKHR1bW9yX3Rpc3N1ZV9zaXRlICE9ICJbTm90IEF2YWlsYWJsZV0iKSAlPiUgCiAgc3VtbWFyaXNlKE4gPSBuKCkpICU+JSAKICBtdXRhdGUoZnJlcSA9IE4gLyBzdW0oTikpCmBgYAoKCjxkaXYgY2xhc3M9ImV4ZXJjaXNlIj4KKipFeGVyY2lzZSoqOiBDcmVhdGUgYSBwbG90IHRvIHNob3cgdGhlIGdlbmRlciBzcGxpdCBpbiBjYXNlcyBvZiBlYWNoIHR1bW9yIHR5cGUuCjwvZGl2PgoKYGBge3IgfQpkYXRhICU+JSAKICBncm91cF9ieSh0dW1vcl90aXNzdWVfc2l0ZSxnZW5kZXIpICU+JSAKICBmaWx0ZXIodHVtb3JfdGlzc3VlX3NpdGUgIT0gIltOb3QgQXZhaWxhYmxlXSIpICU+JSAKICBzdW1tYXJpc2UoTiA9IG4oKSkgJT4lIAogIG11dGF0ZShmcmVxID0gTiAvIHN1bShOKSkgJT4lIAogIGdncGxvdChhZXMoeCA9IGdlbmRlciwgeSA9IGZyZXEpKSArIGdlb21fY29sKCkgKyBmYWNldF93cmFwKH50dW1vcl90aXNzdWVfc2l0ZSkKYGBgCgo8ZGl2IGNsYXNzPSJleGVyY2lzZSI+CioqRXhlcmNpc2UqKjogQ3JlYXRlIGEgcGxvdCB0byBzaG93IHRoZSBwcm9wb3J0aW9uIG9mIHBhdGllbnRzIGRlYWQgb3IgYWxpdmUgZm9yIGVhY2ggdHVtb3VyIHR5cGUKPC9kaXY+CgoKYGBge3J9CmRhdGEgJT4lIAogIGdyb3VwX2J5KHR1bW9yX3Rpc3N1ZV9zaXRlLHZpdGFsX3N0YXR1cykgJT4lIAogIGZpbHRlcih0dW1vcl90aXNzdWVfc2l0ZSAhPSAiW05vdCBBdmFpbGFibGVdIikgJT4lIAogIGZpbHRlcih2aXRhbF9zdGF0dXMgIT0gIltOb3QgQXZhaWxhYmxlXSIpICU+JQogIHN1bW1hcmlzZShOID0gbigpKSAlPiUgCiAgbXV0YXRlKGZyZXEgPSBOIC8gc3VtKE4pKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gdml0YWxfc3RhdHVzLCB5ID0gZnJlcSkpICsgZ2VvbV9jb2woKSArIGZhY2V0X3dyYXAofnR1bW9yX3Rpc3N1ZV9zaXRlKQpgYGAKCg==